package com.zte.claa.inficombo.csa.app.service.gprs.mina;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zte.claa.inficombo.csa.app.config.GPRSDevConnProps;
import com.zte.claa.inficombo.csa.app.service.gprs.GPRSDevService;
import com.zte.claa.inficombo.csa.app.service.gprs.bean.GPRSDevCommand;
import com.zte.claa.inficombo.csa.app.service.gprs.bean.GPRSMessage;
import com.zte.claa.inficombo.csa.app.service.gprs.bean.GPRSMsgHead;

/**
 * author:0283000106
 */
public class GPRSDevServerManager {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	// 连接参数.
	private GPRSDevConnProps connProps;
	private IoAcceptor acceptor = null;
	// 服务句柄
	private GPRSDevService gprsDevService = null;
	
	// sessionid和最近一次上报数据时间戳关系：key=sessionid，value=long（millseconds）
	private Map<Long, Long> sessionTimeMap = null;
	
	// deveui和session的映射关系：key=deveui，value=session
	private Map<String, GPRSDevConnSession> deveuiSessionMap = null;
	
	// sessionid和session的映射关系： key=sessionid,value=session
	private Map<Long, GPRSDevConnSession> sidSessionMap = null;
	
	// 定时规则调度线程池.	
	private ScheduledThreadPoolExecutor timerRuleExecutor = null;

	// 构建器.
	public GPRSDevServerManager(GPRSDevConnProps connProps, GPRSDevService gprsDevService){
		this.connProps = connProps;
		this.gprsDevService = gprsDevService;
		this.deveuiSessionMap = new ConcurrentHashMap<String, GPRSDevConnSession>();
		this.sidSessionMap = new ConcurrentHashMap<Long, GPRSDevConnSession>();
		this.sessionTimeMap = new ConcurrentHashMap<Long, Long>();
		this.timerRuleExecutor = new ScheduledThreadPoolExecutor(1);
		//this.timerRuleExecutor.setMaximumPoolSize(1);  Dubious method used
	}
	
	// 启动服务器（监听端口）
	public void start() throws Exception {
		try{
			this.acceptor = new NioSocketAcceptor();
			// 实例化编解码器。
			GPRSDevMsgDecoder decoder = new GPRSDevMsgDecoder(Charset.forName(this.connProps.getCharsetname()));
			GPRSDevMsgEncoder encoder = new GPRSDevMsgEncoder(Charset.forName(this.connProps.getCharsetname()));
			GPRSDevMsgCodecFactory codecFactory = new GPRSDevMsgCodecFactory(decoder, encoder);
			// 添加到过滤链。
			this.acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codecFactory));
			this.acceptor.getFilterChain().addLast("logger", new LoggingFilter());   
			// 设置handler
			this.acceptor.setHandler(new GPRSDevSvrHandler(this));   
			this.acceptor.getSessionConfig().setReadBufferSize(this.connProps.getBufsize());
			// 设置空闲状态（1分钟无读写，则认为session空闲，需要发版本或时间查询消息。）
			this.acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, this.connProps.getSessionidlesecond());
			this.acceptor.bind(new InetSocketAddress(this.connProps.getSvrport()));
			// 启动session空闲超期检查线程
			this.startSessionIdleExpireChecker();
			logger.info("[*^_^*] GPRSDevConnSession starts ok on port={}", this.connProps.getSvrport());
		}catch(Exception e){
			logger.error("GPRSDevConnSession starts failed!", e);
			this.stop();
			throw new Exception("GPRSDevConnSession starts failed (svrport="+this.connProps.getSvrport()+")", e);
		}
	}
	
	/**
	 * 释放session和connector.
	 */
	public void stop() {
		if(this.acceptor != null){
			try{
				this.acceptor.unbind();
				this.acceptor.dispose();
				this.acceptor = null;
				logger.info("acceptor close ok!");
			}catch(Exception ex){
				logger.error("acceptor close failed!", ex);
			}
		}		
		if(this.sidSessionMap != null){
			// 关闭所有connSession.
			try{
				Iterator<Long> it = this.sidSessionMap.keySet().iterator();
				while(it.hasNext()){
					GPRSDevConnSession connSession = this.sidSessionMap.get(it.next());
					if(connSession != null){
						connSession.stopSession();
					}
				}
				this.sidSessionMap.clear();
				this.sidSessionMap = null;
				logger.info("session close ok!");
			}catch(Exception ex){
				logger.error("Session close failed!", ex);
			}		
		}
		if(this.sessionTimeMap != null){
			this.sessionTimeMap.clear();
			this.sessionTimeMap = null;
		}
		if(this.deveuiSessionMap != null){
			this.deveuiSessionMap.clear();
			this.deveuiSessionMap = null;
		}
		if(this.timerRuleExecutor != null){
			this.timerRuleExecutor.shutdown();
			this.timerRuleExecutor = null;
		}
	}
	
	// session创建
	public void onSessionCreated(IoSession session) throws Exception {
		int curSessionNum = this.deveuiSessionMap.values().size();
		if(curSessionNum >= this.connProps.getMaxconnnum()){
			logger.warn("Connection reaches uplimit {}!", this.connProps.getMaxconnnum());
			throw new Exception("Connection reaches uplimit ("+ this.connProps.getMaxconnnum()+").");
		}
		// 创建一个connserver
		GPRSDevConnSession connSession = new GPRSDevConnSession(session, this.connProps);
		this.sidSessionMap.put(session.getId(), connSession);
		this.sessionTimeMap.put(session.getId(), System.currentTimeMillis());
		logger.info("IoSession created: id={}, remoteaddr={}.", session.getId(), session.getRemoteAddress());
	}

	// session打开.
	public void onSessionOpened(IoSession session) throws Exception {
		if(this.sidSessionMap.get(session.getId()) == null){
			this.onSessionCreated(session);
		}
		if(this.sessionTimeMap.get(session.getId()) == null){
			this.sessionTimeMap.put(session.getId(), System.currentTimeMillis());
		}
	}

	// session关闭.
	public void onSessionClosed(IoSession session) throws Exception {
		logger.info("session closed: " + session.getId()+", remoteAddr="+session.getRemoteAddress());
		GPRSDevConnSession gprssession = this.sidSessionMap.get(session.getId());
		if(null != gprssession){
			this.sidSessionMap.remove(session.getId());
			this.sessionTimeMap.remove(session.getId());
			this.deveuiSessionMap.remove(gprssession.getDeveui());
			gprssession.stopSession();			
		}		
	}
	
	public void closeSessionByDeveui(String deveui) throws Exception {
		GPRSDevConnSession gprssession = this.getDeveuiSessionMap().get(deveui);		
		if(null != gprssession){
			logger.info("session closed: " + gprssession.getSession().getId()+", remoteAddr="+gprssession.getSession().getRemoteAddress());
			//session的关闭最好放在manager里面处理，需要处理全局map对象
			this.sidSessionMap.remove(gprssession.getSessionId());
			this.sessionTimeMap.remove(gprssession.getSessionId());
			this.deveuiSessionMap.remove(deveui);
			gprssession.stopSession();
		}

	}
	
	
	// 会话空闲.
	public void onSessionIdle(IoSession session, IdleStatus status) throws Exception {
		logger.info("session idled: " + session.getId());
		GPRSDevConnSession connSession = this.sidSessionMap.get(session.getId());
		if(connSession != null){
			// 下发时间查询命令，相当于心跳...
			//connSession.sendTimeQueryCmd();
		}
	}

	// 会话出现异常.
	public void OnExceptionCaught(IoSession session, Throwable cause) throws Exception {
		logger.error("exception: " + cause.getMessage()+", remoteAddr="+session.getRemoteAddress(), cause);
	}

	// 接收到消息.
	public void onMessageReceived(IoSession session, Object message) throws Exception {
		logger.info("session message received: " + session.getId()+ ", msg: " + message);
		this.sessionTimeMap.put(session.getId(), System.currentTimeMillis());
		GPRSMessage msg = (GPRSMessage)message;
		//通过message中获取deveui信息
		String deveui = msg.getHead().getDeveui();
		GPRSDevConnSession connSession1 = this.sidSessionMap.get(session.getId());
		GPRSDevConnSession connSession2 = this.deveuiSessionMap.get(deveui);
		if(connSession1 != null && connSession2 != null){
			if(session.getId() != connSession2.getSessionId()){
				//Thread.sleep(1000);
				//dealRepeatRegMsgErr(deveui,connSession1);
				logger.warn("The same deveui cannt register at the same time: " + session.getId()+ ", msg: " + message);
				this.onSessionClosed(connSession2.getSession());
				connSession2 = null;
			}else{
				//dealRepeatRegMsg(deveui,connSession1);
				if(msg.getHead().getMsgtype() == GPRSMsgHead.GPRS_REGREQ){
					msg.setErrtype(GPRSMessage.ERR_MULTICONN);
					logger.info("The same device register again: " + session.getId()+ ", msg: " + msg);
				}
			}
		}
		if(connSession1 != null && connSession2 == null){
			connSession1.setDeveui(deveui);
			this.deveuiSessionMap.put(deveui, connSession1);
		}
		this.gprsDevService.onGPRSDevMsgReport(connSession1, msg);
	}
	
	/**
	 * 下发命令。
	 * 
	 * @param cmd
	 * @throws Exception
	 */
	public WriteFuture sendCommand(long sessionId, GPRSDevCommand msg) throws Exception {
		GPRSDevConnSession connSession = this.deveuiSessionMap.get(String.valueOf(sessionId));
		if(connSession == null){
			throw new Exception("通信链路会话为空！");
		}
		this.sessionTimeMap.put(sessionId, System.currentTimeMillis());
		return connSession.sendCommand(msg);
	}
	
	// 分配新的消息流水号（主动下发命令时才需要）
	public int allocateNewSno(long sessionId) throws Exception {
		GPRSDevConnSession connSession = this.deveuiSessionMap.get(String.valueOf(sessionId));
		if(connSession == null){
			throw new Exception("通信链路会话为空！");
		}
		return connSession.getMessageSno(); 
	}
	
	// 上层应用主动关闭session.
	public void closeConnServer(long sessionId) throws Exception {
		logger.info("session closed by application: " + sessionId);
		GPRSDevConnSession connSession = this.deveuiSessionMap.remove(String.valueOf(sessionId));
		if(connSession != null){
			connSession.stopSession();
		}
	}
	
	// 启动session空闲超期检查线程
	private void startSessionIdleExpireChecker(){
		int periodInSec = this.connProps.getSessionidlesecond();
		this.timerRuleExecutor.scheduleAtFixedRate(new Runnable(){
			@Override
			public void run() {
				checkSessionIdleExpire();
			}
		}, 30, periodInSec, TimeUnit.SECONDS);
	}
	
	private void checkSessionIdleExpire(){
		// 最大三倍的idle间隔...
		long expireTime = this.connProps.getSessionidlesecond()*3*1000L;
		long curTime = System.currentTimeMillis();
		long expLine = curTime - expireTime;
		Iterator<Long> it = this.sessionTimeMap.keySet().iterator();
		while(it.hasNext()){
			Long sessionId = it.next();
			GPRSDevConnSession connSession = this.sidSessionMap.get(sessionId);			
			Long ts = this.sessionTimeMap.get(sessionId);
			if(expLine >= ts){
				this.sidSessionMap.remove(sessionId);
				if(connSession != null){
					this.deveuiSessionMap.remove(connSession.getDeveui());  //Dereference before null check
					connSession.stopSession();
					logger.info("Idle reach uplimit, close svr: "+sessionId);
				}
				it.remove();
			}
		}
	}

	public Map<Long, Long> getSessionTimeMap() {
		return sessionTimeMap;
	}

	public void setSessionTimeMap(Map<Long, Long> sessionTimeMap) {
		this.sessionTimeMap = sessionTimeMap;
	}

	public Map<String, GPRSDevConnSession> getDeveuiSessionMap() {
		return deveuiSessionMap;
	}

	public void setDeveuiSessionMap(Map<String, GPRSDevConnSession> deveuiSessionMap) {
		this.deveuiSessionMap = deveuiSessionMap;
	}

	public Map<Long, GPRSDevConnSession> getSidSessionMap() {
		return sidSessionMap;
	}

	public void setSidSessionMap(Map<Long, GPRSDevConnSession> sidSessionMap) {
		this.sidSessionMap = sidSessionMap;
	}
	
}
